package tfnode

import (
	"encoding/binary"
	"errors"
	"io"
	"net"
	"strconv"
	"strings"
)

//the tfserver packet magic
const TFServerMagic uint8 = 0xF5

//the tfserver op code
const (
	OpError         uint8 = 0xFF
	FullFileRead          = 0x03
	OkFullFileRead        = 0x04
	FullFileWrite         = 0x05
	OkFullFileWrite       = 0x06
	FileDelete            = 0x07
	OkFileDelete          = 0x08

	Watermark   = 0x09
	OkWatermark = 0x0A
	SyncFrom    = 0x0B
	OkSyncFrom  = 0x0C
	SyncTo      = 0x0D
	OkSyncTo    = 0x0E

	ReplWrite    = 0x11
	OkReplWrite  = 0x12
	ReplDelete   = 0x13
	OkReplDelete = 0x14
)

const (
	//the packet header size
	HeaderLength = 20
	//the reserved argument count
	MaxArgCount = 4
)

//the split token within internal keys
const SplitString string = "/"

//the message structure definition
type Packet struct {
	//header
	Magic     uint8
	Opcode    uint8
	Argnum    uint8
	Reserved  uint8 //0x00
	Arglength [MaxArgCount]uint32

	//body
	Args [MaxArgCount][]byte
}

func NewPacket() *Packet {
	pkt := new(Packet)
	pkt.Magic = TFServerMagic
	return pkt
}

func (p *Packet) marshalHeader() (out []byte) {
	out = make([]byte, HeaderLength)
	out[0] = p.Magic
	out[1] = p.Opcode
	out[2] = p.Argnum
	out[3] = p.Reserved

	//argument size in little endian
	for i, arg := range p.Arglength {
		n := 4 + 4*i
		binary.LittleEndian.PutUint32(out[n:n+4], arg)
	}

	return out
}

func (p *Packet) unmarshalHeader(in []byte) error {
	p.Magic = in[0]
	if p.Magic != TFServerMagic {
		return errors.New("Bad Magic " + strconv.Itoa(int(p.Magic)))
	}

	p.Opcode = in[1]
	p.Argnum = in[2]

	//little endian
	for i := range p.Arglength {
		n := 4 + 4*i
		p.Arglength[i] = binary.LittleEndian.Uint32(in[n : n+4])
	}

	return nil
}

func (p *Packet) WriteToConn(c net.Conn) error {
	//firstly the header
	hdrBuf := p.marshalHeader()
	_, err := c.Write(hdrBuf)
	if err != nil {
		return err
	}

	//dirty hack
	if p.Opcode == OkFullFileRead {
		return nil
	}

	//then the body
	for i := 0; i < int(p.Argnum); i++ {
		_, err = c.Write(p.Args[i])

		if err != nil {
			break
		}
	}

	return err
}

func (p *Packet) ReadFromConn(c net.Conn) error {
	hdrBuf := make([]byte, HeaderLength)
	_, err := io.ReadFull(c, hdrBuf)
	if err != nil {
		return err
	}

	err = p.unmarshalHeader(hdrBuf)
	if err != nil {
		return err
	}

	//dirty hack
	if p.Opcode == FullFileWrite {
		return nil
	}

	for i := 0; i < int(p.Argnum); i++ {
		p.Args[i] = make([]byte, p.Arglength[i])
		_, err := io.ReadFull(c, p.Args[i])

		if err != nil {
			return err
		}
	}
	return nil
}

func (p *Packet) ReadData(c net.Conn, size int64) error {
	if p.Args[0] == nil || int64(len(p.Args[0])) != size {
		p.Args[0] = make([]byte, size)
	}
	_, err := io.ReadFull(c, p.Args[0])

	return err
}

func (p *Packet) WriteData(c net.Conn) error {
	_, err := c.Write(p.Args[0])
	return err
}

func (p *Packet) ParseInternalKey() (chunk int, offset, length int64, err error) {
	//parse the first argument, e.g. "5/1024/4096"
	inkeyArray := strings.Split(string(p.Args[0]), SplitString)
	if len(inkeyArray) != 3 {
		err = errors.New("BadInternalKey")
		return
	}

	chunk, err = strconv.Atoi(inkeyArray[0])
	if err == nil {
		offset, err = strconv.ParseInt(inkeyArray[1], 10, 64)
	}
	if err == nil {
		length, err = strconv.ParseInt(inkeyArray[2], 10, 64)
	}
	return
}

func (p *Packet) ParseReplWriteKey() (chunk int, initialOffset, totalSize, offset, length int64, err error) {
	inkeyArray := strings.Split(string(p.Args[0]), SplitString)
	if len(inkeyArray) != 5 {
		err = errors.New("BadReplWriteKey")
		return
	}

	chunk, err = strconv.Atoi(inkeyArray[0])
	if err == nil {
		initialOffset, err = strconv.ParseInt(inkeyArray[1], 10, 64)
	}
	if err == nil {
		totalSize, err = strconv.ParseInt(inkeyArray[2], 10, 64)
	}
	if err == nil {
		offset, err = strconv.ParseInt(inkeyArray[3], 10, 64)
	}
	if err == nil {
		length, err = strconv.ParseInt(inkeyArray[4], 10, 64)
	}

	return
}

func (p *Packet) PackErrorReply(msg string) {
	p.Opcode = OpError
	//then attach the data
	p.Argnum = 1
	p.Arglength[0] = uint32(len([]byte(msg)))
	p.Args[0] = []byte(msg)
}

func (p *Packet) PackWriteReply(chunk int, offset, length int64, crc uint32) {
	p.Opcode = OkFullFileWrite
	p.Argnum = 1

	//pack them into the internal key, e.g. "5/1024/4096/CRC", as the first argument
	argString := strconv.Itoa(chunk) + SplitString + strconv.FormatInt(offset, 10) +
		SplitString + strconv.FormatInt(length, 10) + SplitString + strconv.FormatInt(int64(crc), 16)

	p.Args[0] = []byte(argString)
	p.Arglength[0] = uint32(len(p.Args[0]))
}

func (p *Packet) PackReadReply(size int64) {
	p.Opcode = OkFullFileRead
	p.Argnum = 1
	p.Arglength[0] = uint32(size)
}

func (p *Packet) PackReplWriteRequest(chunk int, initialOffset, totalSize, offset, length int64, data []byte) {
	p.Opcode = ReplWrite
	p.Argnum = 2

	inkey := strconv.Itoa(chunk) +
		SplitString + strconv.FormatInt(initialOffset, 10) +
		SplitString + strconv.FormatInt(totalSize, 10) +
		SplitString + strconv.FormatInt(offset, 10) +
		SplitString + strconv.FormatInt(length, 10)

	p.Args[0] = []byte(inkey)
	p.Arglength[0] = uint32(len(p.Args[0]))
	p.Arglength[1] = uint32(len(data))
	p.Args[1] = data
}

func (p *Packet) PackReplDeleteRequest(inkey []byte) {
	p.Opcode = ReplDelete
	p.Argnum = 1
	p.Arglength[0] = uint32(len(inkey))
	p.Args[0] = inkey
}
